package com.example240705;

import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.persistence.TypedQuery;
import jakarta.persistence.criteria.*;
import org.antlr.v4.runtime.misc.Pair;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

@Controller
//@Transactional
public class QueryController {
    @Autowired
    private StudentReposity studentReposity;

    @Autowired
    private PhysicalRepository physicalRepository;

    @PersistenceContext
    private EntityManager entityManager;

    @GetMapping("/dynamicQuery/{pageSize}/{pageNumber}")
    private @ResponseBody List<StudentInfo>//tested
    dynamicQuery(@RequestParam Map<String, String> all_param,
                 @PathVariable("pageNumber") int pageNumber,
                 @PathVariable("pageSize") int pageSize) {
        CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
        CriteriaQuery<StudentInfo> criteriaQuery = criteriaBuilder.createQuery(StudentInfo.class);
        Root<StudentInfo> root = criteriaQuery.from(StudentInfo.class);
        List<Predicate> predicates = paramParser(all_param, criteriaBuilder, root);

        if (!predicates.isEmpty()) {
            criteriaQuery.where(criteriaBuilder.and(predicates.toArray(new Predicate[0])));
        }

        TypedQuery<StudentInfo> query = entityManager.createQuery(criteriaQuery);
        query.setMaxResults(pageSize).setFirstResult(pageSize * (pageNumber - 1));
        return query.getResultList();
    }

    @GetMapping("/queryTest")
    private @ResponseBody List<PhysicalTest>//tested
    queryTest(@RequestParam(value = "id", defaultValue = "") Long id) {
        if (id == null) {
            return new ArrayList<>();
        } else {
            return physicalRepository.findAllByStudentId(id);
        }
    }

//    @GetMapping("/repoetStudentTest/{pageSize}/{pageNumber}")
//    private @ResponseBody List<Pair<StudentInfo, List<PhysicalTest>>>
//    reportStudentTest(@RequestParam Map<String, String> all_param,
//                      @PathVariable("pageNumber") int pageNumber,
//                      @PathVariable("pageSize") int pageSize) {
//        List<Pair<StudentInfo, List<PhysicalTest>>> res = new ArrayList<>();
//
//        List<StudentInfo> students = dynamicQuery(all_param, pageNumber, pageSize);
//        for (StudentInfo studentInfo : students) {
//            List<PhysicalTest> tests = queryTest(studentInfo.getId());
//            res.add(new Pair<StudentInfo, List<PhysicalTest>>(studentInfo, tests));
//        }
//
//        return res;
//    }


    @GetMapping("/reportST/{pageSize}/{pageNumber}")
    private @ResponseBody List<Pair<StudentInfo, List<PhysicalTest>>>//tested
    reportST(@RequestParam Map<String, String> all_param,
             @PathVariable("pageNumber") int pageNumber,
             @PathVariable("pageSize") int pageSize) {
        List<StudentInfo> students = dynamicQuery(all_param, pageNumber, pageSize);
        List<Long> student_ids = new ArrayList<>();
        for (StudentInfo student : students) {
            student_ids.add(student.getId());
        }
        List<PhysicalTest> physicalTests = physicalRepository.findAllInIds(student_ids);
        List<Pair<StudentInfo, List<PhysicalTest>>> result = new ArrayList<>();
        for (StudentInfo student : students) {
            List<PhysicalTest> tmp = physicalTests.stream()
                    .filter((PhysicalTest pt) -> pt.getStudentId().equals(student.getId())).toList();
            result.add(new Pair<StudentInfo, List<PhysicalTest>>(student, tmp));
        }
        return result;
    }

    @GetMapping("/queryST/{pageSize}/{pageNumber}")
    private @ResponseBody List<StudentPhysicalDTO>//tested
    queryST(@RequestParam Map<String, String> all_param,
            @PathVariable("pageNumber") int pageNumber,
            @PathVariable("pageSize") int pageSize) {
        CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
        CriteriaQuery<StudentPhysicalDTO> criteriaQuery = criteriaBuilder.createQuery(StudentPhysicalDTO.class);
        Root<StudentInfo> root = criteriaQuery.from(StudentInfo.class);
        Join<StudentInfo, PhysicalTest> join = root.join("physicalTests", JoinType.LEFT);

// 构造DTO对象
        criteriaQuery.select(criteriaBuilder.construct(StudentPhysicalDTO.class, root, join));
        List<Predicate> predicates = paramParser(all_param, criteriaBuilder, root);

        if (!predicates.isEmpty()) {
            criteriaQuery.where(criteriaBuilder.and(predicates.toArray(new Predicate[0])));
        }

        return entityManager.createQuery(criteriaQuery)
                .setMaxResults(pageSize).setFirstResult(pageSize * (pageNumber - 1))
                .getResultList();
    }

    @PostMapping("/updateStudent")
    private @ResponseBody String//tested
    updateStudent(@RequestParam Map<String, String> all_param) {
        if (all_param.get("id_seq") == null) {
            return "failed";
        }

        StudentInfo studentInfo = studentReposity.findByIdSeq(all_param.get("id_seq"));
        if (updateStudentHelper(all_param, studentInfo)) {
            return "success";
        } else {
            return "failed";
        }

    }

    @PostMapping("/addStudent")
    private @ResponseBody String//tested
    addStudent(@RequestParam Map<String, String> all_param) {
        if (studentReposity.findByIdSeq(all_param.get("id_seq")) != null) {
            return "failed";
        }
        if (updateStudentHelper(all_param, null)) {
            return "success";
        } else {
            return "failed";
        }
    }

    @PostMapping("/deleteStudent")
    private @ResponseBody String//tested
    deleteStudent(@RequestParam String id_seq) {
        StudentInfo studentInfo = studentReposity.findByIdSeq(id_seq);
        if (studentInfo == null) {
            return "no such student.";
        } else {
            physicalRepository.deleteByStudentId(studentInfo.getId());
            studentReposity.delete(studentInfo);
            return "success";
        }
    }

    @PostMapping("/addPhysicalTest")
    private @ResponseBody String//tested
    addPhysical(@RequestParam Map<String, String> all_param) {
        try {
            Long student_id = (Long) modifyParser(all_param.get("studentId"));
            if (!studentReposity.existsById(student_id)) {
                return "unknown student_id";
            }
        } catch (Exception e) {
            return "failed";
        }

        if (updatePhysicalHelper(all_param, null)) {
            return "success";
        } else {
            return "failed";
        }
    }

    @GetMapping("/deletePhysicalTest")//tested
    private @ResponseBody int deletePhysicalTest(@RequestParam("student_id") Long student_id) {
        return physicalRepository.deleteByStudentId(student_id);
    }


    private Object
    modifyParser(String toParse) {
        if (toParse.isEmpty()) {
            return null;
        } else if (toParse.charAt(0) == 's') {
            return Short.parseShort(toParse.substring(1));
        } else if (toParse.charAt(0) == 'f') {
            return Float.parseFloat(toParse.substring(1));
        } else if (toParse.charAt(0) == 'i') {
            return Integer.parseInt(toParse.substring(1));
        } else if (toParse.charAt(0) == 'l') {
            return Long.parseLong(toParse.substring(1));
        } else {
            return toParse;
        }
    }

    private List<Predicate>
    paramParser(Map<String, String> all_param,
                CriteriaBuilder criteriaBuilder,
                Root<StudentInfo> root) {

        List<Predicate> predicates = new ArrayList<>();
        for (Map.Entry<String, String> entry : all_param.entrySet()) {
            String context = entry.getValue(), name = entry.getKey();
            if (!context.isEmpty() && context.charAt(0) == '%') {
                int segmentation = context.indexOf('$');
                String s1 = context.substring(1, segmentation),
                        s2 = context.substring(segmentation + 1);
                if (!s1.isEmpty()) {
                    predicates.add(criteriaBuilder.greaterThanOrEqualTo(root.get(name), Integer.parseInt(s1)));
                }
                if (!s2.isEmpty()) {
                    predicates.add(criteriaBuilder.lessThanOrEqualTo(root.get(name), Integer.parseInt(s2)));
                }
            } else {
                predicates.add(criteriaBuilder.equal(root.get(name), context));
            }
        }
        return predicates;
    }

    private boolean
    updateStudentHelper(Map<String, String> all_param,
                        StudentInfo studentInfo) {
        if (studentInfo == null) {
            studentInfo = new StudentInfo();
        }
        for (Map.Entry<String, String> entry : all_param.entrySet()) {
            String name = entry.getKey(), context = entry.getValue();
            Object tmp = modifyParser(context);
            try {
                Field field = studentInfo.getClass().getDeclaredField(name);
                field.setAccessible(true);
                field.set(studentInfo, tmp);
            } catch (Exception e) {
                return false;
            }

        }
        studentReposity.save(studentInfo);
        return true;
    }

    private boolean
    updatePhysicalHelper(Map<String, String> all_param,
                         PhysicalTest physicalTest) {
        if (physicalTest == null) {
            physicalTest = new PhysicalTest();
        }
        for (Map.Entry<String, String> entry : all_param.entrySet()) {
            String name = entry.getKey(), context = entry.getValue();
            Object tmp = modifyParser(context);
            try {
                Field field = physicalTest.getClass().getDeclaredField(name);
                field.setAccessible(true);
                field.set(physicalTest, tmp);
            } catch (Exception e) {
                return false;
            }
        }
        physicalRepository.save(physicalTest);
        return true;
    }


//    /////////////////////////////////////////////////////////////////////////////////////
//    @GetMapping("/test")
//    private @ResponseBody void test() {
//        System.out.println(Long.parseLong(""));
//    }
}
